﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
using Moki.Exceptions;
using Moki;
using Moki.Interfaces;
using Moki.State;

namespace Moki.Core
{
    /// <summary>
    /// Creates mocks. Stores proxy objects and interceptors.
    /// </summary>
    internal class MockRepository
    {
        private static readonly MockRepository _instance = new MockRepository(StateController.Instance);
        private readonly ProxyGenerator _proxy = new ProxyGenerator();
        private StateChangedNotifier _notifier;
        private StateController _stateController;

        internal MockRepository(StateController stateController)
        {
            ProxyInterceptorMap = new Dictionary<object, MockInterceptor>();
             _notifier = new StateChangedNotifier();
             _stateController = stateController;
            _stateController.RegisterNotifier(_notifier);
        }

        internal Dictionary<object, MockInterceptor> ProxyInterceptorMap { get; set; }

        public static MockRepository Instance
        {
            get { return _instance; }
        }

        #region Public

        public TType CreateMock<TType>() where TType : class
        {
            return CreateMock<TType>(CallConstraints.Loose);
        }

        public TType CreateMock<TType>(CallConstraints constraints) where TType : class
        {
            return (TType)CreateMock(typeof(TType), null, constraints, null);
        }

        internal object CreateMock(Type type, CallConstraints constraints)
        {
            return CreateMock(type, null, constraints, null);
        }

        internal object CreateMock(Type type, Type[] additionalInterfaces, CallConstraints constraints, object[] constructorArguments)
        {
            MockInterceptor interceptor = new MockInterceptor(constraints, false, this);
            interceptor.StateNotifier = _notifier;

            _stateController.RegisterUpdater(interceptor);

            object proxyObject;
            ProxyGenerationOptions options = new ProxyGenerationOptions();

            Validate(type);

            if (type.IsInterface)
            {
                if (additionalInterfaces != null && additionalInterfaces.Length > 0)
                    proxyObject = _proxy.CreateInterfaceProxyWithoutTarget(type, additionalInterfaces, interceptor);
                else
                    proxyObject = _proxy.CreateInterfaceProxyWithoutTarget(type, interceptor);    
            }
            else
            {
                if (additionalInterfaces != null && additionalInterfaces.Length > 0)
                {
                    if (constructorArguments != null && constructorArguments.Length > 0)
                        proxyObject = _proxy.CreateClassProxy(type, additionalInterfaces, options, constructorArguments, interceptor);
                    else
                        proxyObject = _proxy.CreateClassProxy(type, additionalInterfaces, interceptor);
                }
                else
                {
                    if (constructorArguments != null && constructorArguments.Length > 0)
                        proxyObject = _proxy.CreateClassProxy(type, additionalInterfaces, ProxyGenerationOptions.Default, constructorArguments, interceptor);
                    else
                        proxyObject = _proxy.CreateClassProxy(type, interceptor);
                }

                interceptor.InterceptsPartialMock = true;
            }

            ProxyInterceptorMap.Add(proxyObject, interceptor);
            return proxyObject;
        }

        private void Validate(Type type)
        {
            if (type.IsSealed)
                throw new MockTypeException(String.Format("Can't mock sealed class {0}", type.Name));
            else if (type.IsMarshalByRef)
                throw new MockTypeException(String.Format("Can't mock class {0} because derives MarshalByRefObject.", type.Name));
        }

        public void Cleanup()
        {
            ProxyInterceptorMap = new Dictionary<object, MockInterceptor>();
            _notifier.NotifyStateChanged(MockingState.Acting);
        }

        #endregion

        #region Internal

        /// <summary>
        /// Gets interceptor related with given proxy. 
        /// </summary>
        /// <param name="proxy">Proxy object</param>
        /// <returns>Interceptor</returns>
        public MockInterceptor GetInterceptor(object proxy)
        {
            return ProxyInterceptorMap[proxy];
        }
        
        #endregion
    }
}
